/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.cratos.xxgram.platf.user;

import static com.cratos.xxgram.platf.base.RetCodes.RET_PARAMS_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_QRCODE_REFAUSELOGIN;
import static com.cratos.xxgram.platf.base.RetCodes.RET_QRCODE_STATUSUNEXIT;
import static com.cratos.xxgram.platf.base.RetCodes.RET_QRCODE_TIMEOUT;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_ACCOUNT_EXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_ACCOUNT_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_ACCOUNT_PWD_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_ECDH_UPDATE_FAILED;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_EMAIL_EXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_EMAIL_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_GENDER_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_MOBILE_EXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_MOBILE_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_MOBILE_SMSFREQUENT;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_NOTEXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_PASSWORD_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_QQID_EXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_RANDCODE_EXPIRED;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_RANDCODE_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_SIGNUP_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_STATUS_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_USERNAME_ILLEGAL;
import static com.cratos.xxgram.platf.base.RetCodes.RET_USER_WXID_EXISTS;
import static com.cratos.xxgram.platf.base.RetCodes.retResult;
import static com.cratos.xxgram.platf.base.UserInfo.ECDH_DEFAULT_SEQNUM;
import static com.cratos.xxgram.platf.base.UserInfo.GENDER_FEMALE;
import static com.cratos.xxgram.platf.base.UserInfo.GENDER_MALE;
import static org.redkale.source.FilterExpress.EQUAL;
import static org.redkale.source.FilterExpress.IGNORECASEEQUAL;

import java.io.Serializable;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.persistence.Transient;

import org.redkale.convert.json.JsonConvert;
import org.redkale.net.http.WebSocketNode;
import org.redkale.service.RetResult;
import org.redkale.source.CacheSource;
import org.redkale.source.ColumnValue;
import org.redkale.source.FilterExpress;
import org.redkale.source.FilterNode;
import org.redkale.source.Flipper;
import org.redkale.util.AnyValue;
import org.redkale.util.Comment;
import org.redkale.util.Sheet;
import org.redkale.util.Utility;

import com.cratos.xxgram.platf.base.BaseEntity;
import com.cratos.xxgram.platf.base.BaseService;
import com.cratos.xxgram.platf.base.RetCodes;
import com.cratos.xxgram.platf.base.UserInfo;
import com.cratos.xxgram.platf.file.FileService;
import com.cratos.xxgram.platf.friend.FriendService;
import com.cratos.xxgram.platf.friend.FriendShip;
import com.cratos.xxgram.platf.group.GroupDetail;
import com.cratos.xxgram.platf.group.GroupServiceWs;
import com.cratos.xxgram.platf.group.UserGroup;
import com.cratos.xxgram.platf.message.MessageRecord;
import com.cratos.xxgram.platf.message.NotifyContent;
import com.cratos.xxgram.platf.notice.RandomCode;
import com.cratos.xxgram.platf.notice.RandomCodeHis;
import com.cratos.xxgram.platf.notice.RandomService;
import com.cratos.xxgram.platf.notice.SmsService;
import com.cratos.xxgram.platf.presetsecret.PresetsecretRecord;
import com.cratos.xxgram.platf.presetsecret.SecretRecord;
import com.cratos.xxgram.platf.qrcode.QrCodeService;
import com.cratos.xxgram.platf.rongcloud.RongCloudService;
import com.cratos.xxgram.platf.util.Cryptos;
import com.cratos.xxgram.platf.util.Cryptos.ECDHCrypto;
import com.cratos.xxgram.platf.util.Phones;
import com.cratos.xxgram.platf.util.QueueTask;
import com.cratos.xxgram.platf.util.ShuffleRandom;


/**
 *
 * @author zhangjx
 */
@Comment("用户服务模块")
public class UserService extends BaseService {

    private static final MessageDigest sha1;

    private static final MessageDigest md5;

    public static final String AES_KEY = "XXGRAM__20202020";

    private static final Cipher aesEncrypter; //加密

    private static final Cipher aesDecrypter; //解密

    private static final ECDHCrypto ecdhCrypto;//ecdh 公钥私钥

    @Transient //登录缓存队列
    protected final QueueTask<UserLoginRecord> loginQueue = new QueueTask<>(1);

    @Transient //注销缓存队列
    protected final QueueTask<UserLogoutEntry> logoutQueue = new QueueTask<>(1);

    @Comment("web版本扫描 获取手机端二维码  key= uuid + # + time36, value=jsessionid + # + UNF")
    protected final ConcurrentHashMap<String, String> webLoginKey = new ConcurrentHashMap();

    private static final String READY_SCAN = "0"; //准备扫描
    private static final String BEAN_SCAN = "1"; //已扫描
    public static final String AGREE_LOGIN = "2"; // 同意登录
    private static final String REFAUSE_LOGIN = "3";//拒绝登录

    @Comment("web版本扫描 获取手机端扫码状态  key= uuid + # + time36, value=0 未处理 1 已扫描  2 已同意 3.已拒绝 ")
    protected final ConcurrentHashMap<String, String> webLoginStatus = new ConcurrentHashMap();

    @Comment("定时任务")
    protected ScheduledThreadPoolExecutor scheduler;

    static {
        MessageDigest d = null;
        try {
            d = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        sha1 = d;
        try {
            d = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        md5 = d;

        Cipher cipher = null;
        final SecretKeySpec aesKey = new SecretKeySpec(AES_KEY.getBytes(), "AES");
        try {
            cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        } catch (Exception e) {
            throw new Error(e);
        }
        aesEncrypter = cipher;  //加密
        try {
            cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, aesKey);
        } catch (Exception e) {
            throw new Error(e);
        }
        aesDecrypter = cipher; //解密
        ecdhCrypto = ECDHCrypto.getInstance();// ecdh对象
    }

    // 半个小时 session 失效
    private final int sessionExpireSeconds = 30 * 60;

    @Resource(name = "usersessions")
    protected CacheSource<Integer> sessions;

    @Resource
    protected FileService fileService;

    @Resource
    private SmsService smsService;

    @Resource
    private JsonConvert convert;

    @Resource
    private RandomService randomCodeService;

    @Resource
    private RongCloudService rongService;

    @Resource(name = "wsim")
    protected WebSocketNode webSocketNode;

    @Resource
    private QrCodeService qrCodeService;

    @Transient //随机源
    protected final SecureRandom idRandom = ShuffleRandom.createRandom();

    @Resource
    private FriendService friendService;
    @Resource
    private GroupServiceWs groupService;
    @Override
    public void init(AnyValue conf) {
        initLoginParam();

        scheduler = new ScheduledThreadPoolExecutor(2, (Runnable r) -> {
            final Thread t = new Thread(r, "usersetvice -task-Thread");
            t.setDaemon(true);
            return t;
        });
//         web版本扫描自动登录  有效期 3分钟
        this.scheduler.scheduleAtFixedRate(() -> {
            try {
                Enumeration<String> keys = webLoginKey.keys();
                long now = System.currentTimeMillis();
                Iterator<String> key = keys.asIterator();
                while (key.hasNext()) {
                    String next = key.next();
                    String uuid = next.substring(0, next.lastIndexOf("#"));
                    String time36 = next.substring(next.lastIndexOf("#") + 1, next.length());
                    long time = Long.parseLong(time36, 36);
                    // 有效期一分钟
                    if (now > (time + 3 * 60 * 1000)) {
                        webLoginKey.remove(next);
                        webLoginStatus.remove(next);
                        fileService.removeQrCodeImg(uuid);
                    }
                }
            } catch (Throwable e) {
                logger.log(Level.SEVERE, "scheduleAtFixedRate CoinPoolRecord error", e);
            }
        }, 1, 10 * 1000, TimeUnit.MILLISECONDS);
    }

    @Comment("登录登出管理")
    private void initLoginParam() {
        loginQueue.init(logger, loginrecord -> source.insert(loginrecord));//登录日志处理
        Consumer<UserLogoutEntry> logoutConsumer = (entry) -> {
            long onlineseconds = 0;
            UserLoginRecord record = source.find(UserLoginRecord.class, FilterNode
            			.create("sessionid", entry.sessionid)
            			.and("createtime", FilterExpress.EQUAL, entry.logintime));
            if (record != null) {
                record.setLogouttime(System.currentTimeMillis());
                onlineseconds = (record.getLogouttime() - record.getCreatetime()) / 1000;
                record.setOnlineseconds(onlineseconds);
                source.updateColumn(record, "onlineseconds", "logouttime");
            }
            // 最后在线时间
            source.updateColumn(UserInfo.class, entry.userid, ColumnValue.mov("logouttime", System.currentTimeMillis()));
            source.updateColumn(UserDetail.class, entry.userid, ColumnValue.mov("logouttime", System.currentTimeMillis()));
        };
        logoutQueue.init(logger, logoutConsumer);
    }

    @Override
    public void destroy(AnyValue conf) {
        logger.log(Level.FINEST, "logoutQueue.size = " + logoutQueue.size());
        logoutQueue.destroy();
        loginQueue.destroy();
    }

    public UserInfo findUserInfo(int userid) {
        if (userid == UserInfo.USERID_SYSTEM) {
            return UserInfo.USER_SYSTEM;
        }
        return source.find(UserInfo.class, userid);
    }

    @Comment("根据账号查找用户")
    public UserInfo findUserInfoByAccount(String account) {
        if (account == null || account.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("account", IGNORECASEEQUAL, account));
    }

    @Comment("根据手机号码查找用户")
    public UserInfo findUserInfoByMobile(int nation, String mobile) {
        if (mobile == null || mobile.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("mobile", EQUAL, mobile).and("nation", EQUAL, nation));
    }

    @Comment("根据邮箱地址查找用户")
    public UserInfo findUserInfoByEmail(String email) {
        if (email == null || email.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("email", IGNORECASEEQUAL, email));
    }

    @Comment("根据微信绑定ID查找用户")
    public UserInfo findUserInfoByWxunionid(String wxunionid) {
        if (wxunionid == null || wxunionid.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("wxunionid", EQUAL, wxunionid));
    }

    @Comment("根据QQ绑定ID查找用户")
    public UserInfo findUserInfoByQqopenid(String qqopenid) {
        if (qqopenid == null || qqopenid.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("qqopenid", EQUAL, qqopenid));
    }

    @Comment("根据APP设备ID查找用户")
    public UserInfo findUserInfoByApptoken(String apptoken) {
        if (apptoken == null || apptoken.isEmpty()) {
            return null;
        }
        return source.find(UserInfo.class, FilterNode.create("apptoken", EQUAL, apptoken));
    }

    @Comment("查询用户列表， 通常用于后台管理系统查询")
    public Sheet<UserDetail> queryUserDetail(FilterNode node, Flipper flipper) {
        return source.querySheet(UserDetail.class, flipper, node);
    }

    @Comment("根据登录态获取当前用户信息")
    public UserInfo current(String sessionid) {
        Integer userid = sessions.getAndRefresh(sessionid, sessionExpireSeconds);
        UserInfo user = null;
        if (userid != null) {
            user = findUserInfo(userid);
        }
        if (user != null) {
            user.setJsessionid(sessionid);
        }
        return userid == null ? null : user;
    }

    @Comment("发送短信验证码")
    public RetResult smscode(final short type, final int nation, String mobile) {
        int vcode = 0;
        if (mobile == null) {
            return new RetResult(RET_USER_MOBILE_ILLEGAL, type + " mobile is null"); //手机号码无效
        }
        // 
        if ((vcode = validateMobile(nation, mobile)) > 0) {
            return RetCodes.retResult(vcode);
        }
        if (mobile.indexOf('+') == 0) {
            mobile = mobile.substring(1);
        }
        UserInfo info = findUserInfoByMobile(nation, mobile);
        if (type == RandomCode.TYPE_SMSREG || type == RandomCode.TYPE_SMSMOB) { //手机注册或手机修改的号码不能已存在
            if (info != null) {
                return new RetResult(RET_USER_MOBILE_EXISTS);
            }
        } else if (type == RandomCode.TYPE_SMSPWD) { //修改密码
            if (info == null) {
                return new RetResult(RET_USER_NOTEXISTS);
            }
        } else if (type == RandomCode.TYPE_SMSLGN) { //手机登录或手机注册
//            if (info == null) {
//                return new RetResult(RET_USER_NOTEXISTS, "smslgn mobile " + mobile + " not exists");
//            }
        } else if (type == RandomCode.TYPE_SMSODM) { //原手机
            if (info == null) {
                return new RetResult(RET_USER_NOTEXISTS);
            }
        } else {
            return new RetResult(RET_PARAMS_ILLEGAL);
        }
        List<RandomCode> codes = randomCodeService.queryRandomCodeByMobile(mobile);
        if (!codes.isEmpty()) {
            RandomCode last = codes.get(codes.size() - 1);
            if (last.getCreatetime() + 60 * 1000 > System.currentTimeMillis()) {
                return RetCodes.retResult(RET_USER_MOBILE_SMSFREQUENT);
            }
        }
        final int smscode = RandomCode.randomSmsCode();
//        int smscode = 123456;
        try {
            RetResult sendRandomSmsCode = smsService.sendRandomSmsCode(type, nation + mobile, smscode);
            if (!sendRandomSmsCode.isSuccess()) {
                return sendRandomSmsCode;
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "mobile(" + mobile + ", type=" + type + ") send smscode " + smscode + " error", e);
            return retResult(RET_USER_MOBILE_SMSFREQUENT);
        }
        RandomCode code = new RandomCode();
        code.setCreatetime(System.currentTimeMillis());
        if (info != null) {
            code.setUserid(info.getUserid());
        }
        code.setRandomcode(nation + mobile + "-" + smscode);
        code.setType(type);
        randomCodeService.createRandomCode(code);
        return RetResult.success();
    }

    @Comment("用户密码登录")
    public RetResult<UserInfo> login(LoginBean bean) {
        final RetResult<UserInfo> result = new RetResult();
        UserInfo user = null;
        if (bean != null && !bean.emptyCookieinfo() && bean.emptyAccount()) {
            String cookie;
            try {
                cookie = decryptAES(bean.getCookieinfo());
            } catch (Exception e) {
                return retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
            }
            int sharp = cookie.indexOf('#');
            if (sharp > 0) {
                bean.setApptoken(cookie.substring(0, sharp));
            }
            int pos = cookie.indexOf('$');
            int userid = Integer.parseInt(cookie.substring(sharp + 1, pos), 36);
            user = this.findUserInfo(userid);
            
            logger.finest("bean ===" + bean + ",&&&&&&  user =" + user);
            if (user != null) {
                char type = cookie.charAt(pos + 1);
                int wen = cookie.indexOf('?');
                String val = wen > 0 ? cookie.substring(pos + 2, wen) : cookie.substring(pos + 2);
                if (type == '0') { //密码
                    bean.setPassword(val);
                }
            }
            logger.finest("bean 2222222" + bean);
        }
        if (bean == null /*|| bean.emptySessionid() **/ || (user == null && bean.emptyAccount())) {
            return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
        }
        String key = "";
        if (!bean.emptyAccount()) {
            //融云版本的登录
            if (LoginBean.LOGIN_WITH_MOBILE == bean.getLoginmode()) {
                key = "mobile";
                user = findUserInfoByMobile(bean.getNation(), bean.getAccount());
            }

            if (LoginBean.LOGIN_WITH_ID == bean.getLoginmode()) {
                key = "userid";
                user = source.find(UserInfo.class, FilterNode.create("userid", FilterExpress.EQUAL, bean.getAccount()));
            }
            if (LoginBean.LOGIN_WITH_VERCODE == bean.getLoginmode()) {
                RetResult<RandomCode> rr = checkRandomCode(bean.getNation() + bean.getAccount(), bean.getVercode(), RandomCode.TYPE_SMSLGN);
                if (!rr.isSuccess()) {
                    return RetCodes.retResult(rr.getRetcode());
                }
                key = "mobile";
                user = findUserInfoByMobile(bean.getNation(), bean.getAccount());
            }
            // 新注册的账户
            if (LoginBean.LOGIN_WITH_REGISTER_SUCCESS == bean.getLoginmode()) {
                key = "mobile";
                user = findUserInfoByMobile(bean.getNation(), bean.getAccount());
            }
        }

        if (user == null) { //不在缓存内
            UserDetail detail = source.find(UserDetail.class, key, bean.getAccount());
            if (detail == null) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
            }
            user = detail.createUserInfo();
            logger.finest("bean ===" + bean + ",****  userdetail =" + detail);
            if (bean.getPassword().isEmpty() && !bean.getVercode().isEmpty()) { //手机验证码登录
                RetResult<RandomCode> rr = checkRandomCode(bean.getNation() + detail.getMobile(), bean.getVercode(), RandomCode.TYPE_SMSLGN);
                if (!rr.isSuccess()) {
                    return RetCodes.retResult(rr.getRetcode());
                }
            } else if (!detail.getPassword().equals(digestPassword(bean.getPassword()))
            			// 如果是 手机号码登录 密码是加密 三次后的密码
            			&& !detail.getPassword().equals(bean.getPassword())) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
            }
            user = detail.createUserInfo();
            result.setRetcode(0);
            result.setResult(user);
        } else { //在缓存内
            // 验证码 登录
            if (LoginBean.LOGIN_WITH_VERCODE == bean.getLoginmode()) {
                if (!bean.getVercode().isEmpty()) { //手机验证码登录
                    RetResult<RandomCode> rr = checkRandomCode(bean.getNation() + user.getMobile(), bean.getVercode(), RandomCode.TYPE_SMSLGN);
                    if (!rr.isSuccess()) {
                        return RetCodes.retResult(rr.getRetcode());
                    }
                    removeRandomCode(rr.getResult());
                } else if (!user.getPassword().equals(digestPassword(bean.getPassword()))) {
                    return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
                }
            }
            //ID 登录
            if (LoginBean.LOGIN_WITH_ID == bean.getLoginmode()
                    && !user.getPassword().equals(digestPassword(bean.getPassword()))) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
            }
            // 融云版本登录
            if (LoginBean.LOGIN_WITH_MOBILE == bean.getLoginmode()
                    && !user.getPassword().equals(digestPassword(bean.getPassword())) 
                    // 如果是 手机号码登录 密码是加密 三次后的密码
                    && !user.getPassword().equals(bean.getPassword())) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
            }
            result.setRetcode(0);
            result.setResult(user);
            if (!user.getApptoken().equals(bean.getApptoken())) { //用户设备变更了
                user.setAppos(bean.getAppos());
                user.setApptoken(bean.getApptoken());
                source.updateColumn(UserDetail.class, user.getUserid(), ColumnValue.mov("appos", bean.getAppos()), ColumnValue.mov("apptoken", bean.getApptoken()));
                source.updateColumn(UserInfo.class, user.getUserid(), ColumnValue.mov("appos", bean.getAppos()), ColumnValue.mov("apptoken", bean.getApptoken()));
            }
        }
        // 设置极光推送注册id
        if(null != bean.getJpregid()) {
        	updateJpushRegid(user, bean.getJpregid());
        }
        user.setJsessionid(bean.getSessionid());// 将session 添加到 用户对象 返回个websocket刷新失效的session 免登录
        this.createUserLoginRecord(user, bean);// 记录登录信息
        this.sessions.set(sessionExpireSeconds, bean.getSessionid(), result.getResult().getUserid());
        result.setResult(user);

        return result;
    }

    @Comment("用户密码登录")
    public RetResult<UserInfo> loginBak(LoginBean bean) {
        final RetResult<UserInfo> result = new RetResult();
        UserInfo user = null;
        boolean unok = true;
        if (bean != null && !bean.emptyCookieinfo() && bean.emptyAccount()) {
            String cookie;
            try {
                cookie = decryptAES(bean.getCookieinfo());
            } catch (Exception e) {
                return retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
            }
            int sharp = cookie.indexOf('#');
            if (sharp > 0) {
                bean.setApptoken(cookie.substring(0, sharp));
            }
            int pos = cookie.indexOf('$');
            int userid = Integer.parseInt(cookie.substring(sharp + 1, pos), 36);
            user = this.findUserInfo(userid);
            if (user != null) {
                char type = cookie.charAt(pos + 1);
                int wen = cookie.indexOf('?');
                String val = wen > 0 ? cookie.substring(pos + 2, wen) : cookie.substring(pos + 2);
                if (type == '0') { //密码
                    bean.setPassword(val);
                }
            }
        }
        if (bean == null || bean.emptySessionid() || (user == null && bean.emptyAccount())) {
            return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
        }
        String key = "";
        if (user == null && !bean.emptyAccount()) {
            if (bean.getAccount().indexOf('@') > 0) {
                key = "email";
                user = findUserInfoByEmail(bean.getAccount());
            } else if (Character.isDigit(bean.getAccount().charAt(0))) {
                key = "mobile";
                user = findUserInfoByMobile(bean.getNation(), bean.getAccount());
            } else {
                key = "account";
                user = findUserInfoByAccount(bean.getAccount());
            }
        }
        if (user == null) { //不在缓存内
            UserDetail detail = source.find(UserDetail.class, key, bean.getAccount());
            if (detail == null) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);
            }
            if (bean.getPassword().isEmpty() && !bean.getVercode().isEmpty()) { //手机验证码登录
                RetResult<RandomCode> rr = checkRandomCode(detail.getMobile(), bean.getVercode(), RandomCode.TYPE_SMSLGN);
                if (!rr.isSuccess()) {
                    return RetCodes.retResult(rr.getRetcode());
                }
                removeRandomCode(rr.getResult());
            } else if (!detail.getPassword().equals(digestPassword(bean.getPassword()))) {
                return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
            }
            user = detail.createUserInfo();
            result.setRetcode(0);
            result.setResult(user);
            source.insert(user);
        } else { //在缓存内
            if (unok) {
                if (bean.getPassword().isEmpty() && !bean.getVercode().isEmpty()) { //手机验证码登录
                    RetResult<RandomCode> rr = checkRandomCode(user.getMobile(), bean.getVercode(), RandomCode.TYPE_SMSLGN);
                    if (!rr.isSuccess()) {
                        return RetCodes.retResult(rr.getRetcode());
                    }
                    removeRandomCode(rr.getResult());
                } else if (!user.getPassword().equals(digestPassword(bean.getPassword()))) {
                    return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL); //用户或密码错误   
                }
            }
            result.setRetcode(0);
            result.setResult(user);
            if (!user.getApptoken().equals(bean.getApptoken())) { //用户设备变更了
                user.setAppos(bean.getAppos());
                user.setApptoken(bean.getApptoken());
                source.updateColumn(UserDetail.class, user.getUserid(), ColumnValue.mov("appos", bean.getAppos()), ColumnValue.mov("apptoken", bean.getApptoken()));
                source.updateColumn(UserInfo.class, user.getUserid(), ColumnValue.mov("appos", bean.getAppos()), ColumnValue.mov("apptoken", bean.getApptoken()));
            }
        }
        this.createUserLoginRecord(user, bean);// 记录登录信息
        this.sessions.set(sessionExpireSeconds, bean.getSessionid(), result.getResult().getUserid());
        return result;
    }

    private void createUserLoginRecord(UserInfo user, LoginBean bean) {
        UserLoginRecord record = bean.createUserLoginRecord(user);
        loginQueue.add(record);
    }

    @Comment("用户注册")
    public RetResult<UserInfo> register(UserDetail user) {
        RetResult<UserInfo> result = new RetResult();
        if (user == null) {
            return RetCodes.retResult(RET_USER_SIGNUP_ILLEGAL);
        }
        if (user.getAccount().isEmpty() && user.getMobile().isEmpty()) {
            return RetCodes.retResult(RET_USER_SIGNUP_ILLEGAL);
        }
        short gender = user.getGender();
        if (gender != 0 && gender != GENDER_MALE && gender != GENDER_FEMALE) {
            return RetCodes.retResult(RET_USER_GENDER_ILLEGAL);
        }
        int retcode = 0;
        if (!user.getMobile().isEmpty() && (retcode = checkMobile(user.getNation(), user.getMobile())) != 0) {
            return RetCodes.retResult(retcode);
        }
        user.setCreatetime(System.currentTimeMillis());
        user.setInfotime(user.getCreatetime());
        user.setUpdatetime(0);
        // ECDH 更新私钥公钥
        try {
            KeyPair keyPair = ecdhCrypto.generateKeyPairNamedCurve(Cryptos.ECParams.secp160k1Key);
            PrivateKey privateKey = keyPair.getPrivate(); //私钥
            PublicKey publicKey = keyPair.getPublic(); //公钥
            String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
            String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
            user.setEcdhprivkey(privateKeyStr);
            user.setEcdhpubkey(publicKeyStr);
            user.setEcdhseqnum(ECDH_DEFAULT_SEQNUM);// 默认从100开始
        } catch (Exception e) {
            e.printStackTrace();
            logger.finest("ecdh create error " + e.getMessage());
        }
        user.setStatus(UserInfo.STATUS_NORMAL);
        // 二次加密
        if (!user.getPassword().isEmpty()) {
            user.setPassword(digestPassword(secondPasswordMD5(user.getPassword())));
        }
        //        int maxid = source.getNumberResult(UserDetail.class, FilterFunc.MAX, 3_0000_0000, "userid", (FilterNode) null).intValue();
        int userid = 3_0000_0000 + idRandom.nextInt(500_0000);//随机生成群id

        boolean ok = false;
        boolean emptyname = user.getUsername() == null || user.getUsername().isEmpty();
        for (int i = 0; i < 20; i++) {
            try {
                user.setUserid(userid);
                if (emptyname) {
                    user.setUsername("用户" + Integer.toString(user.getUserid(), 36));
                }
                source.insert(user);
                ok = true;
                break;
            } catch (Exception e) { //并发时可能会重复创建， 忽略异常
                logger.log(Level.INFO, "create userdetail error: " + user, e);
//                maxid = source.getNumberResult(UserDetail.class, FilterFunc.MAX, 3_0000_0000, "userid", (FilterNode) null).intValue();
                userid = 3_0000_0000 + idRandom.nextInt(500_0000);
            }
        }
        if (!ok) {
            return RetCodes.retResult(RET_PARAMS_ILLEGAL);
        }
        // 添加融云注册服务
        RetResult<String> rongrs = rongService.insertUser(user);
        if (rongrs.isSuccess()) {
            user.setRytoken(rongrs.getResult());
            user.setRytime(user.getCreatetime());
            source.updateColumn(user, "rytoken", "rytime");
        }
        //------------------------扩展信息-----------------------------
        UserInfo info = user.createUserInfo();
        source.insert(info);
        result.setResult(info);
        return result;
    }

    @Comment("更新密钥")
    public RetResult updateEcdhKeyPair(UserInfo user) {
        // ECDH 更新私钥公钥
        RetResult result = new RetResult();
        try {
            KeyPair keyPair = ecdhCrypto.generateKeyPairNamedCurve(Cryptos.ECParams.secp160k1Key);
            PrivateKey privateKey = keyPair.getPrivate(); //私钥
            PublicKey publicKey = keyPair.getPublic(); //公钥
            String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
            String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());
            user.setEcdhprivkey(privateKeyStr);
            user.setEcdhpubkey(publicKeyStr);
            user.setEcdhseqnum(ECDH_DEFAULT_SEQNUM);// 默认从100开始
            UserDetail cuser = source.find(UserDetail.class, user.getUserid());
            cuser.setEcdhseqnum(1 + cuser.getEcdhseqnum());
            // 上次公钥私钥记录下来
            cuser.setPreecdhprivkey(user.getEcdhprivkey());
            cuser.setPreecdhpubkey(user.getEcdhpubkey());
            cuser.setEcdhseqnum(cuser.getEcdhseqnum() + 1);// 更换私钥
            // 根据上次的公钥进行加密
            PublicKey prePublicKey = ecdhCrypto.readPublicKey(cuser.getPreecdhpubkey());
            String currentPrivateKey = ecdhCrypto.encrypt(user.getEcdhprivkey(), prePublicKey);
            source.updateColumn(cuser, "ecdhprivkey", "ecdhpubkey", "preecdhprivkey", "preecdhpubkey", "ecdhseqnum");
            result.setResult(user);
            result.attach("ecdhprivkey", currentPrivateKey);
        } catch (Exception e) {
            e.printStackTrace();
            logger.finest("ecdh create error " + e.getMessage());
            return RetCodes.retResult(RET_USER_ECDH_UPDATE_FAILED);// 生成加密信息失败
        }
        return result;
    }

    @Comment("注销登录")
    public boolean logout(final String sessionid) {
        UserInfo user = current(sessionid);
        if (user != null && !user.getApptoken().isEmpty()) {
            user.setAppos("");
            user.setApptoken("");
            source.updateColumn(UserDetail.class, user.getUserid(), ColumnValue.mov("appos", ""), ColumnValue.mov("apptoken", ""));
            // 更新注销时间
            source.updateColumn(UserInfo.class, user.getUserid(), ColumnValue.mov("appos", ""), ColumnValue.mov("apptoken", ""),
                    ColumnValue.mov("logouttime", System.currentTimeMillis()));
            logoutQueue.add(new UserLogoutEntry(user.getUserid(), sessionid, user.getLogintime()));
        }
        sessions.remove(sessionid);
        return true;
    }

    protected static class UserLogoutEntry extends BaseEntity {

        public int userid;

        public String sessionid;

        public long logintime;
        
        public UserLogoutEntry(int userid, String sessionid, long logintime) {
            this.userid = userid;
            this.sessionid = sessionid;
            this.logintime = logintime;
        }
    }

    public RetResult updateUsername(int userid, String username) {
        if (username == null || username.isEmpty()) {
            return RetCodes.retResult(RET_USER_USERNAME_ILLEGAL);
        }
        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        if (user.getUsername().equals(username)) {
            return RetResult.success();
        }
        if (username.isEmpty()) {
            return RetCodes.retResult(RET_USER_USERNAME_ILLEGAL);
        }
        // 用户名长度20
        if(username.length() > UserInfo.USERNAME_MAXLEN) {
        	return RetCodes.retResult(RET_USER_USERNAME_ILLEGAL);
        }
        UserDetail ud = new UserDetail();
        ud.setUserid(userid);
        ud.setUsername(username);
        ud.setInfotime(System.currentTimeMillis());
        source.updateColumn(ud, "username", "infotime");
        user.setUsername(username);
        user.setInfotime(ud.getInfotime());
        source.updateColumn(user, "username", "infotime");
        return RetResult.success();
    }

    public RetResult updateApptoken(int userid, String appos, String apptoken) {
        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        if (appos == null) {
            appos = "";
        }
        if (apptoken == null) {
            apptoken = "";
        }
        source.updateColumn(UserDetail.class, user.getUserid(), ColumnValue.mov("appos", appos.toLowerCase()), ColumnValue.mov("apptoken", apptoken));
        source.updateColumn(UserInfo.class, user.getUserid(), ColumnValue.mov("appos", appos.toLowerCase()), ColumnValue.mov("apptoken", apptoken));
        user.setAppos(appos.toLowerCase());
        user.setApptoken(apptoken);
        return RetResult.success();
    }

    public RetResult updateFace(int userid, String face) {
        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        if (user.getFace().equals(face)) {
            return RetResult.success();
        }
        UserDetail ud = new UserDetail();
        ud.setUserid(userid);
        ud.setFace(face);
        ud.setInfotime(System.currentTimeMillis());
        source.updateColumn(ud, "face", "infotime");
        user.setFace(face);
        user.setInfotime(ud.getInfotime());
        source.updateColumn(user, "face", "infotime");
        
        // 更新用户申请头像
        rongService.updateUser(user);//更新融云用户头像
        // 更新用户信息
        {
        	friendService.updateAskFriendInfo(user);
        	groupService.updateGroupFace(face, userid);
        }
        
        return RetResult.success();
    }

    public RetResult updateGender(int userid, short gender) {
        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        if (gender != GENDER_MALE && gender != GENDER_FEMALE) {
            return RetCodes.retResult(RET_USER_GENDER_ILLEGAL);
        }
        UserDetail ud = new UserDetail();
        ud.setUserid(userid);
        ud.setGender(gender);
        ud.setInfotime(System.currentTimeMillis());
        source.updateColumn(ud, "gender", "infotime");
        user.setGender(gender);
        user.setInfotime(ud.getInfotime());
        source.updateColumn(user, "gender", "infotime");
        return RetResult.success();
    }

    //precode 表示原手机号码收到的短信验证码，如果当前用户没有配置手机号码，则该值忽略
    public RetResult updateMobile(int userid, int nation, String newmobile, String vercode, String precode) {
        int retcode = checkMobile(nation, newmobile);
        if (retcode != 0) {
            return RetCodes.retResult(retcode);
        }
        RandomCode code = source.find(RandomCode.class, nation + newmobile + "-" + vercode);
        if (code == null) {
            return RetCodes.retResult(RET_USER_RANDCODE_ILLEGAL);
        }
        if (code.isExpired()) {
            return RetCodes.retResult(RET_USER_RANDCODE_EXPIRED);
        }

        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        RandomCode rc = null;
        if (!user.getMobile().isEmpty()) {
            rc = source.find(RandomCode.class, user.getNation() + "" + user.getMobile() + "-" + precode);
            if (rc == null) {
                return RetCodes.retResult(RET_USER_RANDCODE_ILLEGAL);
            }
            if (rc.isExpired()) {
                return RetCodes.retResult(RET_USER_RANDCODE_EXPIRED);
            }
        }
        //更新电话号码
        source.updateColumn(UserDetail.class, FilterNode.create("userid", user.getUserid()), ColumnValue.mov("mobile", newmobile), ColumnValue.mov("nation", nation));
        source.updateColumn(UserInfo.class, FilterNode.create("userid", user.getUserid()), ColumnValue.mov("mobile", newmobile), ColumnValue.mov("nation", nation));
        //        source.updateColumn(UserDetail.class, user.getUserid(), "mobile", newmobile);
        //        source.updateColumn(UserInfo.class, user.getUserid(), "mobile", newmobile);
        user.setMobile(newmobile);
        code.setUserid(user.getUserid());
        source.insert(code.createRandomCodeHis(RandomCodeHis.RETCODE_OK));
        source.delete(RandomCode.class, code.getRandomcode());
        if (rc != null) {
            source.insert(rc.createRandomCodeHis(RandomCodeHis.RETCODE_OK));
            source.delete(RandomCode.class, rc.getRandomcode());
        }
        return RetResult.success();
    }

    public RetResult<UserInfo> updatePwd(UserPwdBean bean) {
        UserInfo user = bean.getSessionid() == null ? null : current(bean.getSessionid());
        final String newpwd = digestPassword(secondPasswordMD5(bean.getNewpwd())); //HEX-MD5(密码明文)
        if (user == null) {  //表示忘记密码后进行重置密码
            bean.setSessionid(null);
            String randomcode = bean.getRandomcode();
            if (randomcode == null || randomcode.isEmpty()) {
                if (bean.getAccount() != null && !bean.getAccount().isEmpty()
                        && bean.getVercode() != null && !bean.getVercode().isEmpty()) {
                    randomcode = bean.getAccount() + "-" + bean.getVercode();
                }
            }
            if (randomcode != null && !randomcode.isEmpty()) {
                RandomCode code = source.find(RandomCode.class, randomcode);
                if (code == null || code.getType() != RandomCode.TYPE_SMSPWD) {
                    return retResult(RET_USER_RANDCODE_ILLEGAL);
                }
                if (code.isExpired()) {
                    return RetCodes.retResult(RET_USER_RANDCODE_EXPIRED);
                }

                user = findUserInfo((int) code.getUserid());
                if (user == null) {
                    return RetCodes.retResult(RET_USER_NOTEXISTS);
                }

                source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
                source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
                user.setPassword(newpwd);
                source.insert(code.createRandomCodeHis(RandomCodeHis.RETCODE_OK));
                source.delete(RandomCode.class, code.getRandomcode());
                return new RetResult<>(user);
            }
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        //用户或密码错误
        if (!Objects.equals(user.getPassword(), digestPassword(secondPasswordMD5(bean.getOldpwd())))) {
            return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);  //原密码错误
        }
        source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
        source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
        user.setPassword(newpwd);
        return new RetResult<>(user);
    }

    @Comment("非验证码设置密码")
    public RetResult<UserInfo> updatePassword(UserInfo user, UserPwdBean bean) {
        final String newpwd = digestPassword(secondPasswordMD5(bean.getNewpwd())); //HEX-MD5(密码明文)
        //用户或密码错误 修改密码
        if (UserPwdBean.UPDATE_PWD == bean.getHaspwd()
                && !Objects.equals(user.getPassword(), digestPassword(secondPasswordMD5(bean.getOldpwd())))) {
            return RetCodes.retResult(RET_USER_ACCOUNT_PWD_ILLEGAL);  //原密码错误
        }
        source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
        source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
        user.setPassword(newpwd);
        return new RetResult<>(user);
    }

    @Comment("短信验证忘记密码")
    public RetResult<UserInfo> resetPwd(UserPwdBean bean) {
        UserInfo user = bean.getSessionid() == null ? null : current(bean.getSessionid());
        final String newpwd = digestPassword(secondPasswordMD5(bean.getNewpwd())); //HEX-MD5(密码明文)
        if (user == null) {  //表示忘记密码后进行重置密码
            bean.setSessionid(null);
            String randomcode = bean.getRandomcode();
            if (randomcode == null || randomcode.isEmpty()) {
                if (bean.getAccount() != null && !bean.getAccount().isEmpty()
                        && bean.getVercode() != null && !bean.getVercode().isEmpty()) {
                    randomcode = bean.getAccount() + "-" + bean.getVercode();
                }
            }
            if (randomcode != null && !randomcode.isEmpty()) {
                RandomCode code = source.find(RandomCode.class, randomcode);
                if (code == null || code.getType() != RandomCode.TYPE_SMSPWD) {
                    return retResult(RET_USER_RANDCODE_ILLEGAL);
                }
                if (code.isExpired()) {
                    return RetCodes.retResult(RET_USER_RANDCODE_EXPIRED);
                }

                user = findUserInfo((int) code.getUserid());
                if (user == null) {
                    return RetCodes.retResult(RET_USER_NOTEXISTS);
                }

                source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
                source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
                user.setPassword(newpwd);
                source.insert(code.createRandomCodeHis(RandomCodeHis.RETCODE_OK));
                source.delete(RandomCode.class, code.getRandomcode());
                return new RetResult<>(user);
            }
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
        source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
        user.setPassword(newpwd);
        return new RetResult<>(user);
    }

    @Comment("web版本重置用户密码")
    public RetResult resetPwdByAdmin(int userid) {
    	UserInfo user = source.find(UserInfo.class, userid);
    	final String pwd = RandomCode.random8Code(); //生成 8位随机数密码
    	if (user == null) {
    		return retResult(RET_USER_NOTEXISTS);
    	}
        final String newpwd = digestPassword(secondPasswordMD5(UserInfo.md5(pwd.getBytes()))); //HEX-MD5(密码明文)
        source.updateColumn(UserDetail.class, user.getUserid(), "password", newpwd);
        source.updateColumn(UserInfo.class, user.getUserid(), "password", newpwd);
        user.setPassword(newpwd);
        return RetResult.success(pwd);
    }
    
    protected UserDetail findUserDetail(int userid) {
        return source.find(UserDetail.class, userid);
    }

    public RetResult<RandomCode> checkRandomCode(String targetid, String randomcode, short type) {
        if (randomcode == null || randomcode.isEmpty()) {
            return RetCodes.retResult(RET_USER_RANDCODE_ILLEGAL);
        }
        if (targetid != null && targetid.length() > 5 && randomcode.length() < 30) {
            randomcode = targetid + "-" + randomcode;
        }
        RandomCode code = source.find(RandomCode.class, randomcode);
        if (code != null && type > 0 && code.getType() != type) {
            return RetCodes.retResult(RET_USER_RANDCODE_ILLEGAL);
        }
        return code == null ? RetCodes.retResult(RET_USER_RANDCODE_ILLEGAL) : (code.isExpired() ? RetCodes.retResult(RET_USER_RANDCODE_EXPIRED) : new RetResult(code));
    }

    public void removeRandomCode(RandomCode code) {
        source.insert(code.createRandomCodeHis(RandomCodeHis.RETCODE_OK));
        source.delete(RandomCode.class, code.getRandomcode());
    }

    private static final Predicate<String> accountReg = Pattern.compile("^[a-zA-Z][\\w_.]{6,64}$").asPredicate();

    /**
     * 检测账号是否有效, 返回0表示手机号码可用 账号不能以数字开头、不能包含@ ， 用于区分手机号码和邮箱
     *
     * @param account
     *
     * @return
     */
    public int checkAccount(String account) {
        if (account == null) {
            return RET_USER_ACCOUNT_ILLEGAL;
        }
        if (!accountReg.test(account)) {
            return RET_USER_ACCOUNT_ILLEGAL;
        }
        return source.exists(UserInfo.class, FilterNode.create("account", IGNORECASEEQUAL, account)) ? RET_USER_ACCOUNT_EXISTS : 0;
    }

    private static final Predicate<String> mobileReg = Pattern.compile("^[1][3,4,5,6,7,8,9][0-9]{9}$").asPredicate();
    final static Pattern pattern = Pattern.compile("^[1][3,4,5,6,7,8,9][0-9]{9}$");
    // 

    /**
     * 检测手机号码是否有效, 返回0表示手机号码可用
     *
     * @param mobile
     *
     * @return
     */
    public int checkMobile(int nation,String mobile) {
        if (mobile == null) {
            return RET_USER_MOBILE_ILLEGAL;
        }
        int ret  = 0;
        if ((ret = validateMobile(nation, mobile)) > 0) {
            return ret;
        }
        return source.exists(UserInfo.class, FilterNode.create("mobile", EQUAL, mobile)) ? RET_USER_MOBILE_EXISTS : 0;
    }

    @Comment("校验手机号码")
    public int validateMobile(int nation, String mobile) {
    	
    	// 统一国际号码校验
    	if(Phones.checkPhoneNumber(mobile, nation)) {
    		return 0;
    	}
    	return RET_USER_MOBILE_ILLEGAL;
		//    	if (nation == 86 || nation == 0) {
		//    		if (mobile == null) {
		//                return RET_USER_MOBILE_ILLEGAL;
		//            }
		//            if (!mobileReg.test(mobile)) {
		//                return RET_USER_MOBILE_ILLEGAL;
		//            }
		//    	}
		//    	// 美国
		//    	if (nation == 1) {
		//    		if (mobile == null) {
		//                return RET_USER_MOBILE_ILLEGAL;
		//            }
		//    		// 电话号码长度为10  
		//            if (mobile.length() != 10 ) {
		//                return RET_USER_MOBILE_ILLEGAL;
		//            }
		//    	}
		//    	
    	//        return 0;
    }
    private static final Predicate<String> emailReg = Pattern.compile("^(\\w|\\.|-)+@(\\w|-)+(\\.(\\w|-)+)+$").asPredicate();

    /**
     * 检测邮箱地址是否有效, 返回0表示邮箱地址可用.给新用户注册使用
     *
     * @param email
     *
     * @return
     */
    public int checkEmail(String email) {
        if (email == null) {
            return RET_USER_EMAIL_ILLEGAL;
        }
        if (!emailReg.test(email)) {
            return RET_USER_EMAIL_ILLEGAL;
        }
        return source.exists(UserInfo.class, FilterNode.create("email", IGNORECASEEQUAL, email)) ? RET_USER_EMAIL_EXISTS : 0;
    }

    public int checkWxunionid(String wxunionid) {
        if (wxunionid == null || wxunionid.isEmpty()) {
            return 0;
        }
        return source.exists(UserInfo.class, FilterNode.create("wxunionid", EQUAL, wxunionid)) ? RET_USER_WXID_EXISTS : 0;
    }

    public int checkQqopenid(String qqopenid) {
        if (qqopenid == null || qqopenid.isEmpty()) {
            return 0;
        }
        return source.exists(UserInfo.class, FilterNode.create("qqopenid", EQUAL, qqopenid)) ? RET_USER_QQID_EXISTS : 0;
    }

    //AES加密
    public static String encryptAES(String value) {
        if (value == null || value.isEmpty()) {
            return value;
        }
        try {
            synchronized (aesEncrypter) {
                return Utility.binToHexString(aesEncrypter.doFinal(value.getBytes()));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //AES解密
    public static String decryptAES(String value) {
        if (value == null || value.isEmpty()) {
            return value;
        }
        byte[] hex = Utility.hexToBin(value);
        try {
            synchronized (aesEncrypter) {
                return new String(aesDecrypter.doFinal(hex));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //第二次MD5
    public static String secondPasswordMD5(String passwordoncemd5) {
        byte[] bytes = ("REDKALE-" + passwordoncemd5.trim().toLowerCase()).getBytes();
        synchronized (md5) {
            bytes = md5.digest(bytes);
        }
        return new String(Utility.binToHex(bytes));
    }

    //第三次密码加密
    public static String digestPassword(String passwordtwicemd5) {
        if (passwordtwicemd5 == null || passwordtwicemd5.isEmpty()) {
            return passwordtwicemd5;
        }
        byte[] bytes = (passwordtwicemd5.trim().toLowerCase() + "-REDKALE").getBytes();
        synchronized (sha1) {
            bytes = sha1.digest(bytes);
        }
        return new String(Utility.binToHex(bytes));
    }

    @Comment("更新用户资料")
    public RetResult<UserInfo> updateUserInfo(int userId, String field, Serializable value) {
        UserInfo userInfo = null;
        int res = source.updateColumn(UserDetail.class, userId, field, value);
        source.updateColumn(UserInfo.class, userId, field, value);
        if (res > 0) {
            UserDetail userDetail = source.find(UserDetail.class, userId);
            userInfo = userDetail.createUserInfo();
        }
        return RetResult.success(userInfo);
    }

    public RetResult destroyAccount(UserInfo userinfo) {
    	if (userinfo == null) return retResult(RET_USER_NOTEXISTS);
        
        //List<FriendShip> friendShips = source.queryList(FriendShip.class, FilterNode.create("userid", EQUAL, userinfo.getUserid()).or("friendid", EQUAL, userinfo.getUserid()));
        List<UserGroup> userGroups = source.queryList(UserGroup.class, FilterNode.create("userid", EQUAL, userinfo.getUserid()));
        List<GroupDetail> groupDetails = source.queryList(GroupDetail.class, FilterNode.create("createuserid", EQUAL, userinfo.getUserid()));
        long now = System.currentTimeMillis();
        // 保存入库
        userGroups.forEach((obj)-> {
        	source.insert(obj.createTableHis(now));
        });
        
        // 群组入库
        groupDetails.forEach(obj -> {
        	source.insert(obj.createTableHis(now));
                //群组注销删除群成员
                groupService.dismissGroup(obj.getGroupid());
        });
        
        source.delete(UserDetail.class, userinfo.getUserid());
        int rs = source.delete(UserInfo.class, userinfo.getUserid());
        //清理数据
        source.delete(FriendShip.class, FilterNode.create("userid", EQUAL, userinfo.getUserid()).or("friendid", EQUAL, userinfo.getUserid()));
        
        //清理数据
        source.delete(FriendShip.class, FilterNode.create("userid", EQUAL, userinfo.getUserid()).or("friendid", EQUAL, userinfo.getUserid()));
        source.delete(com.cratos.xxgram.platf.group.UserGroup.class, FilterNode.create("userid", EQUAL, userinfo.getUserid()));
        //source.delete(com.cratos.xxgram.platf.group.GroupDetail.class, FilterNode.create("createuserid", EQUAL, userinfo.getUserid()));
        
        // 删除密性相关数据
        source.delete(SecretRecord.class, FilterNode.create("userid", FilterExpress.EQUAL, userinfo.getUserid()));
        source.delete(PresetsecretRecord.class, FilterNode
        			.create("friendid", FilterExpress.EQUAL, userinfo.getUserid())
        			.or("userid", FilterExpress.EQUAL, userinfo.getUserid()));
        
        return RetResult.success(rs);
    }

    @Comment("完善用户资料 头像 性别 生日 头像")
    public RetResult improvePersonalInfo(UserDetail userDetail) {

        source.updateColumn(UserDetail.class, userDetail.getUserid(),
                ColumnValue.mov("gender", userDetail.getGender()),
                ColumnValue.mov("username", userDetail.getUsername()),
                ColumnValue.mov("birthday", userDetail.getBirthday()),
                ColumnValue.mov("personlsign", userDetail.getPersonlsign()),
                ColumnValue.mov("face", userDetail.getFace()));

        source.updateColumn(UserInfo.class, userDetail.getUserid(),
                ColumnValue.mov("gender", userDetail.getGender()),
                ColumnValue.mov("username", userDetail.getUsername()),
                ColumnValue.mov("birthday", userDetail.getBirthday()),
                ColumnValue.mov("personlsign", userDetail.getPersonlsign()),
                ColumnValue.mov("face", userDetail.getFace()));

        rongService.updateUser(userDetail);//更新融云用户头像
        
        {
        	friendService.updateAskFriendInfo(userDetail);// 更新用户头像
        	groupService.updateGroupFace(userDetail.getFace(), userDetail.getUserid());
        }
        return RetResult.success(userDetail);
    }

    @Comment("更新解锁密码 ")
    public RetResult updateLockpwd(UserInfo userInfo, String pwd, String oldpwd) {
       int rows = 0;
       if (userInfo.getLockpwd().isEmpty()) {
          rows = source.updateColumn(UserInfo.class, FilterNode.create("userid", EQUAL, userInfo.getUserid()), ColumnValue.mov("lockpwd", pwd));
          rows = source.updateColumn(UserDetail.class, FilterNode.create("userid", EQUAL, userInfo.getUserid()), ColumnValue.mov("lockpwd", pwd));
       } else {
           rows = source.updateColumn(UserInfo.class, FilterNode.create("userid", EQUAL, userInfo.getUserid())
                .and(FilterNode.create("lockpwd", EQUAL, oldpwd)), ColumnValue.mov("lockpwd", pwd));
           rows = source.updateColumn(UserDetail.class, FilterNode.create("userid", EQUAL, userInfo.getUserid())
                .and(FilterNode.create("lockpwd", EQUAL, oldpwd)), ColumnValue.mov("lockpwd", pwd));
       }
        if (rows > 0) {
            return RetResult.success(rows);
        }
        //密码设置无效
        return RetCodes.retResult(RET_USER_PASSWORD_ILLEGAL);
    }

    @Comment("根据状态为更新配置状态")
    public RetResult updateSystemSetting(UserInfo userInfo, int index, int value) {
        UserDetail userDetail = source.find(UserDetail.class, userInfo.getUserid());
        long oldVal = userDetail.getSyssetting();
        index = index - 1;
        long decVal = (long) Math.pow(2, index);
        long yuVal = 0xFFFFFFFF - decVal;
        long cValue = yuVal & oldVal;//减少  当前位的值  获得其他位数的值
        // 判断当前位数的值是否需要增加
        if (value == 1) {
            cValue = cValue + decVal;
        }
        //更新字段值
        userInfo.setSyssetting(cValue);// 更新当前值
        source.updateColumn(UserDetail.class, userInfo.getUserid(), ColumnValue.mov("syssetting", cValue));
        source.updateColumn(UserInfo.class, userInfo.getUserid(), ColumnValue.mov("syssetting", cValue));
        return RetResult.success(cValue);
    }

    /**
     * 更新用户状态
     *
     * @param userid 用户id
     * @param status 用户的新状态
     *
     * @return
     */
    public RetResult updateStatus(int userid, short status) {
        UserInfo user = findUserInfo(userid);
        if (user == null) {
            return RetCodes.retResult(RET_USER_NOTEXISTS);
        }
        if (user.getStatus() == status) {
            return RetResult.success();
        }
        if (status < 10 || status > 90 || status % 10 != 0) {
            return RetCodes.retResult(RET_USER_STATUS_ILLEGAL);
        }
        UserDetail ud = new UserDetail();
        ud.setUserid(userid);
        ud.setStatus(status);
        source.updateColumn(user, "status");
        if (status != UserInfo.STATUS_NORMAL) {
            webSocketNode.forceCloseWebSocket(userid).join();
        }
        return RetResult.success();
    }

    /**
     * 更新极光推送 设备注册id
     *
     * @param userDetail
     * @return
     */
    public RetResult updateJpushRegid(UserInfo userInfo, String jpregid) {
        //        if (jpregid == null || jpregid.isEmpty()) {
        //            return RetCodes.retResult(RET_PARAMS_ILLEGAL);
        //        }
        userInfo.setJpregid(jpregid);
        source.updateColumn(UserDetail.class, userInfo.getUserid(), ColumnValue.mov("jpregid", jpregid));
        source.updateColumn(UserInfo.class, userInfo.getUserid(), ColumnValue.mov("jpregid", jpregid));
        return RetResult.success();
    }

    @Comment("生成 web扫描唯一 id = uuid + 36time ")
    public RetResult preWebLogin() {
        final String uuid = Utility.uuid();
        String uuidKey = uuid + "#" + Utility.format36time(System.currentTimeMillis());
        webLoginKey.put(uuidKey, uuidKey);
        webLoginStatus.put(uuidKey, READY_SCAN);//准备扫描
        final String url = "/user/scanWebLogin?uuidKey=%s";
        RetResult result = qrCodeService.encode(String.format(url, uuidKey), uuid);
        if (!result.isSuccess()) {
            return result;
        }
        Map<String, Object> data = new HashMap<>();
        data.put("uuidKey", uuidKey);
        data.put("filename", result.getResult());
        result.setResult(data);
        return result;
    }

    @Comment("手机端扫描 检验扫描状态")
    public RetResult scanWebLogin(String uuidKey) {
        if (!webLoginStatus.containsKey(uuidKey)) {
            return RetCodes.retResult(RET_QRCODE_TIMEOUT);//验证码失效
        }
        webLoginStatus.put(uuidKey, BEAN_SCAN);//已扫描状态
        return RetResult.success();
    }

    @Comment("手机端扫描 检验是否失效")
    public RetResult okWebLogin(String uuidKey, String token, String loginstatus) {
        if (!webLoginKey.containsKey(uuidKey)) {
            return RetCodes.retResult(RET_QRCODE_TIMEOUT);//验证码失效
        }
        webLoginStatus.put(uuidKey, loginstatus);//已扫描状态
        if (REFAUSE_LOGIN.equalsIgnoreCase(loginstatus)) {
            return RetCodes.retResult(RET_QRCODE_REFAUSELOGIN);//拒绝登录
        }
        if (AGREE_LOGIN.equalsIgnoreCase(loginstatus)) {
            webLoginKey.put(uuidKey, token);
            return RetResult.success();
        }
        return RetCodes.retResult(RET_QRCODE_STATUSUNEXIT);
    }

    @Comment("返回登录状态")
    public RetResult checkScanStatus(String uuidKey) {
        if (!webLoginStatus.containsKey(uuidKey)) {
            return RetCodes.retResult(RET_QRCODE_TIMEOUT);//验证码失效
        }
        return RetResult.success(webLoginStatus.get(uuidKey));
    }

    @Comment("成功返回token")
    public RetResult checkWebLogin(String uuidKey) {
        if (!webLoginKey.containsKey(uuidKey)) {
            return RetCodes.retResult(RET_QRCODE_TIMEOUT);//验证码失效
        }
        //手机端未扫描
        if (uuidKey.equals(webLoginKey.get(uuidKey))) {
            return RetCodes.retResult(RetCodes.RET_MOBILE_CANNOT_SCAN);//手机端未扫描
        }
        String value = webLoginKey.get(uuidKey);
        webLoginKey.remove(uuidKey);
        return RetResult.success(value);
    }

    public RetResult logoutH5(UserInfo userInfo) {
    	 MessageRecord message = new MessageRecord();
         message.setFromid(userInfo.getUserid());
         message.setTargetid(userInfo.getUserid());
         message.setMsgkind(MessageRecord.MSG_KIND_SYSTEM);
         message.setMsgtype(NotifyContent.OBJ_LOGOUT_WEBCLI);
         NotifyContent.LogOutH5Notify notify = new NotifyContent.LogOutH5Notify();
         notify.setExtra(userInfo);
         message.setContent(JsonConvert.root().convertTo(notify));
         return RetResult.success(message);
    }
    
    public RetResult disableH5Status(UserInfo userInfo) {
   	 MessageRecord message = new MessageRecord();
   	 	long now = System.currentTimeMillis();
   	 	message.setMessageid(userInfo.getUserid() + "-" +userInfo.getUserid() + Utility.format36time(now));
        message.setFromid(userInfo.getUserid());
        message.setTargetid(userInfo.getUserid());
        message.setMsgkind(MessageRecord.MSG_KIND_SYSTEM);
        message.setMsgtype(NotifyContent.OBJ_DISABLEAPP_H5);
        message.setCreatetime(now);
        NotifyContent.DisableH5Status notify = new NotifyContent.DisableH5Status();
        notify.setExtra(userInfo);
        message.setContent(JsonConvert.root().convertTo(notify));
        return RetResult.success(message);
   }
    public static void main(String[] args) throws Exception {
        System.out.println("===" + mobileReg.test("13044889046"));
        System.out.println("reg ==" + pattern.matcher("13044889046").matches());
        UserService service = org.redkale.boot.Application.singleton(UserService.class);
        UserDetail userDetail = new UserDetail();
        userDetail.setUserid(300000002);
        service.source.insert(userDetail);
        String d = "1";
        String zero = "";
        for (int index = 1; index < 10; index++) {
            zero += "0";
            long i = (int) Math.pow(2, index - 1);
            System.out.println("index data === >> " + Integer.parseInt(d + zero, 2) + "pow ==" + i + "xxxxx ===>>" + (index <<= 1) + "  cccc =====>> " + ((index - 1) << 2));
        }
		//          String d = new String(new byte[] {0x0A});
		//          System.out.println("d" + d);
		//          System.out.println(md5(new byte[]{0x0A}));
    }
}
